feat(gardener): phase G-h — kind tabs + pagination, group-paged near-duplicates (gh-#657) - #659
Merged
Conversation
…licates page by whole group (STORY-383, gh-#657) IRotFindingStore.ListWithMediaAsync returns RotFindingPage (Items + Total). Flat kinds page rows with an exact row total; near_duplicate limit/offset count distinct group_keys (asc, NULLs excluded) and return every non-resolved member of each selected group — state scopes which groups qualify, never which members render, and qualification applies the same resolved-exclusion as rendering, so no group can qualify while rendering zero rows. Dismissed members render (still duplicates); resolved members don't (system closed them — a stale group_key must not let Keep-this-one bulk-ineligible a track that is no longer a duplicate). Kind-less read keeps the T377 shape verbatim; controller change is mechanical only — total reaches the wire at T386. 4 opus review rounds; every finding re-proven closed on live Postgres. Story383: 17 facts green. Smoked through the production binary on :5236. Claude-Session: https://claude.ai/code/session_01716w9dNYBWSbmuchPQep8A
…the wire (STORY-382, gh-#657) RotFindingPage.Total becomes int? — null iff the read was kind-less, so the controller's `page.Total is int total` gate emits { groups, total } for kind-scoped calls and the byte-compatible T377 { groups } shape otherwise; the store stays the single writer of the distinction. Totals unit: groups for near_duplicate, rows for every other kind. 400/clamp posture untouched. Story382 wire specs un-pinned: 9 facts over real HTTP (WebApplicationFactory + ephemeral Postgres), incl. the gh-#654 un-scoped starvation pin and the exactly-["groups"] shape pin. Opus review PASS + 4 LOWs folded. Smoked through the production binary: near-dup total counts groups (2, not 4 rows). Claude-Session: https://claude.ai/code/session_01716w9dNYBWSbmuchPQep8A
…: kind tabs + pagination (STORY-381/382, gh-#657; closes gh-#654, gh-#655) Server-rendered /gardener reads ?tab=&page=&limit= — five badged TabStrip kind tabs, per-tab kind-scoped fetch parallel with /api/status, "Page N of M" from the response total (groups for near-duplicates), size picker 25/50/100/ 250 default 25, URL-only. GardenerView's client fetch-on-mount (the gh-#654 starvation) is deleted; verbs refresh via router.refresh(). Purge trigger reads "Purge dead tracks…" (gh-#655). Page number clamps so the derived offset stays inside Int32 — paging values are hints, never 400s. The pager is extracted to components/ui/pager.tsx and the catalog converts to it (markup byte-identical). Status-fetch failure degrades to unbadged tabs; near-duplicate suppresses its fallback count rather than unit-swapping. 2 opus rounds (4 MED + 5 LOW folded). Jest 1215/0; tsc, eslint, next build clean. Browser-smoked against the production binary: badges, both pages, picker, tab-switch semantics, whole-cluster card with the resolved member absent and the dismissed member present. Claude-Session: https://claude.ai/code/session_01716w9dNYBWSbmuchPQep8A
# Conflicts: # admin-ui/__specs__/gardener-pagination.spec.tsx # admin-ui/__specs__/gardener-tabs.spec.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📄 Phase G-h — the Gardener queue at scale (gh-#657 · closes #654, #655, #657)
Stacked on #658 (the pending specs — merge it first and this PR shrinks to the three build commits).
What ships
2f39410— store: kind-scoped paging with exact totals; near-duplicates page by whole group (distinctgroup_keyasc). State scopes which groups qualify, never which members render; dismissed members render, resolved members don't (a stalegroup_keymust never let Keep-this-one bulk-ineligible an ex-duplicate — proven live in review, twice, from both directions). 17 real-Postgres facts.83ecada— wire: kind-scopedGET /api/gardener/findingscarriestotal(groups for near_duplicate, rows otherwise) viaRotFindingPage.Total: int?— the store owns the distinction, the controller projects it. Un-scoped responses byte-compatible with the old shape (pinned to exactly["groups"]). 9 wire facts over real HTTP.e50e51a— the page, rebuilt on the catalog idiom: server-rendered?tab=&page=&limit=, five badged kind tabs, "Page N of M" fromtotal, size picker 25/50/100/250 (default 25, URL-only), verbs viarouter.refresh(). The gh-gardener: flat 1000-row findings page ordered by kind starves every later kind to zero rows #654 flat-fetch starvation defect is deleted withGardenerView; gh-gardener: "Purge unavailable…" button label reads as "purge is unavailable" #655's label folds in ("Purge dead tracks…"). Pager extracted tocomponents/ui/pager.tsx(catalog converted, markup byte-identical). Page numbers clamp inside Int32 — paging params are hints, never 400s.Review + verification
7 opus review rounds across the three tasks (2 FAIL + surgical r3/r4 on T385; 1 PASS + LOWs on T386; 1 FAIL + 1 PASS on T387) — every finding re-proven closed on live Postgres or in the rendered DOM, never taken from the report. Suites at the tip: MediaLibrary 1156, Host 2488, Core 181, Architecture 119, Context/Orchestration/Tts green, admin-ui Jest 1215/0, tsc/eslint/next build clean. Browser-smoked against the production binary: badges, both pages, picker, tab-switch semantics, and the whole-cluster card showing the dismissed member with the resolved member absent.
SPEC riders: F153.9/F153.10 (2026-08-31) + the two T385-review amendments (dismissed/resolved member semantics; qualification symmetry).
After merge: v5.5.1 pins+changelog PR on your word (T388) — Abstractions stays 5.5.0.
https://claude.ai/code/session_01716w9dNYBWSbmuchPQep8A